from django.views.generic import ListView,View
from django.shortcuts import render,redirect,get_object_or_404,HttpResponseRedirect
from django.contrib import messages  # 用于添加操作提示信息
from base import models as baseModels
from django.db.models import F,Q,Max,Value, CharField
from django.db import transaction
from django import forms
from django.core.files import File
from django.forms.models import model_to_dict
from django.utils import timezone
from django.conf import settings
import ast,datetime
import json,os,shutil
from django.contrib.auth.mixins import LoginRequiredMixin
class WlView(LoginRequiredMixin,ListView):
    """
    HomeView for website views.
    ListView 参数
    model 指定模型类（如 model = Book），自动查询该模型的所有数据
    queryset 自定义查询集（如 queryset = Book.objects.filter(price__gt=50)），优先级高于 model
    ordering 指定默认排序方式（如 ordering = ['-publish_date'] 或 ordering = '-price'）
    paginate_by 分页大小（如 paginate_by = 10 表示每页显示 10 条）
    paginate_orphans 最后一页允许的最小剩余条数（如 paginate_orphans = 3，避免最后一页只有 1-2 条数据）
    allow_empty 是否允许空列表（默认 True，空列表时正常显示；设为 False 会返回 404）
    template_name 自定义模板路径（如 template_name = 'myapp/book_list.html'）
    template_name_suffix 模板名后缀（默认 _list，结合模型名自动生成模板名，如 book_list.html）
    context_object_name 模板中使用的列表变量名（默认是模型名小写加 _list，如 book_list；可自定义为 books）
    get_context_data()	 重写该方法添加额外上下文（如传递分类、统计数据等）
    def get_context_data(self, **kwargs):
        context = super().get_context_data(** kwargs)
        context['category'] = "小说"  # 额外传递分类信息
        context['total_count'] = self.get_queryset().count()  # 总条数
        return context
    page_kwarg 分页参数名（默认 page，如 URL 中的 ?page=2；可改为 p 即 ?p=2）
    paginator_class 自定义分页器类（默认 django.core.paginator.Paginator）
    http_method_names 允许的 HTTP 方法（默认 ['get', 'head', 'options']，通常无需修改）
    extra_context 静态额外上下文（字典形式，如 extra_context = {'title': '书籍列表'}）

    """

    @classmethod
    def get_detail_data(cls, request):
        detail_data = []
        report_code = request.POST.get('report_code')

        search_q = request.POST.get('q', '').strip()
        # 状态筛选（select_type）：默认 None（不过滤）
        select_type = request.POST.get('select_type')  # 启用，停用

        material_type = request.POST.get('material_type_search', 'all')
        # 物料类型与模型的映射关系（替代冗长的if-elif）
        MODEL_MAP = {
            'raw': baseModels.RawModel,
            'additive': baseModels.AdditiveModel,
            'packaging': baseModels.PackagingModel,
            'intermediate': baseModels.IntermediateModel,
            'finished': baseModels.FinishedModel,
        }
        if material_type == 'all':
            raw_query = MODEL_MAP.get('raw').objects.annotate(
                wl=Value('raw', output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type).all()
            additive_query = MODEL_MAP.get('additive').objects.annotate(
                wl=Value('additive', output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type).all()
            packaging_query = MODEL_MAP.get('packaging').objects.annotate(
                wl=Value('packaging', output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type).all()
            intermediate_query = MODEL_MAP.get('intermediate').objects.annotate(
                wl=Value('intermediate', output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type).all()
            finished_query = MODEL_MAP.get('finished').objects.annotate(
                wl=Value('finished', output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type).all()

            # 3. 根据参数过滤查询集
            # 注意：union 后的查询集是 "联合查询集"，需先转为列表再过滤，或使用 Q 对象（视模型字段兼容性而定）

            # 重新合并过滤后的查询集
            queryset = raw_query.union(additive_query, all=True).union(packaging_query, all=True
                                                                       ).union(intermediate_query, all=True).union(
                finished_query, all=True)
        else:
            queryset = MODEL_MAP.get(material_type).objects.annotate(
                wl=Value(material_type, output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type)

        # 4. 最终排序（必须在过滤后执行，否则可能失效）
        # # 获取数据并转换为字典
        items = queryset.order_by('-update_at').all()
        for item in items:
            item_dict = model_to_dict(item)
            # 关键：添加注解的 wl 字段（item.wl 是 annotate 生成的值）
            item_dict['wl'] = item.wl  # 新增这一行
            # 处理日期时间类型（转换为字符串）
            for key, value in item_dict.items():
                if isinstance(value, timezone.datetime):
                    item_dict[key] = value.strftime('%Y-%m-%d %H:%M:%S')
                elif isinstance(value, datetime.date):
                    item_dict[key] = value.strftime('%Y-%m-%d')
            detail_data.append(item_dict)
        return detail_data

    # model = baseModels.RawModel
    # ordering = ['id']  # 必加：指定默认排序（避免 Django 警告，也能解决部分元数据初始化问题）
    context_object_name = 'wl_list'  # 可选：自定义模板变量名
    paginate_by =8  # 可选：分页
    template_name='base/rawmodel_list.html'
    def get_queryset(self):
        # 2. 获取前端提交的搜索参数（从 self.request.GET 中提取）
        # 搜索关键词（q）：默认空字符串
        search_q = self.request.GET.get('q', '').strip()
        # 状态筛选（select_type）：默认 None（不过滤）
        select_type = self.request.GET.get('select_type')
        material_type = self.request.GET.get('material_type_search','all')
        # 物料类型与模型的映射关系（替代冗长的if-elif）
        MODEL_MAP = {
            'raw': baseModels.RawModel,
            'additive': baseModels.AdditiveModel,
            'packaging': baseModels.PackagingModel,
            'intermediate': baseModels.IntermediateModel,
            'finished': baseModels.FinishedModel,
        }
        if material_type=='all':
            raw_query = MODEL_MAP.get('raw').objects.annotate(
                wl=Value('raw', output_field=CharField())
            ).all()
            additive_query = MODEL_MAP.get('additive').objects.annotate(
                wl=Value('additive', output_field=CharField())
            ).all()
            packaging_query = MODEL_MAP.get('packaging').objects.annotate(
                wl=Value('packaging', output_field=CharField())
            ).all()
            intermediate_query = MODEL_MAP.get('intermediate').objects.annotate(
                wl=Value('intermediate', output_field=CharField())
            ).all()
            finished_query = MODEL_MAP.get('finished').objects.annotate(
                wl=Value('finished', output_field=CharField())
            ).all()
            # 合并查询集（注意：union 后需重新指定排序，原模型的 order_by 会失效）
            # queryset = raw_query.union(additive_query, all=True)

            # 3. 根据参数过滤查询集
            # 注意：union 后的查询集是 "联合查询集"，需先转为列表再过滤，或使用 Q 对象（视模型字段兼容性而定）
            # 以下示例假设两个模型都有 `name`（品名）和 `status`（状态，0=启用，1=停用）字段
            if search_q:
                # 过滤出 "品名包含搜索关键词" 的记录（使用 Q 对象实现模糊查询）
                # 注意：需确保两个模型都有 `name` 字段，否则会报错
                raw_query = raw_query.filter(name__icontains=search_q)
                additive_query = additive_query.filter(name__icontains=search_q)
                packaging_query = packaging_query.filter(name__icontains=search_q)
                intermediate_query = intermediate_query.filter(name__icontains=search_q)
                finished_query = finished_query.filter(name__icontains=search_q)

                # 重新合并过滤后的查询集
                # queryset = raw_query.union(additive_query, all=True)

            if select_type is not None and select_type in ['0', '1']:
                # 过滤状态（1=启用，0=停用），需确保两个模型都有 `status` 字段
                # 转换为整数（假设 status 是整数类型）

                raw_query = raw_query.filter(state=select_type)
                additive_query = additive_query.filter(state=select_type)
                packaging_query = packaging_query.filter(state=select_type)
                intermediate_query = intermediate_query.filter(state=select_type)
                finished_query = finished_query.filter(state=select_type)



            # 重新合并过滤后的查询集
            queryset = raw_query.union(additive_query, all=True).union(packaging_query, all=True
                        ).union(intermediate_query, all=True).union(finished_query, all=True)
        else:
            raw_query = MODEL_MAP.get(material_type).objects.annotate(
                wl=Value(material_type, output_field=CharField())
            ).all()
            if search_q:
                raw_query = raw_query.filter(name__icontains=search_q)
            if select_type is not None and select_type in ['0', '1']:
                # 过滤状态（1=启用，0=停用），需确保两个模型都有 `status` 字段
                # 转换为整数（假设 status 是整数类型）
                raw_query = raw_query.filter(state=select_type)
            queryset = raw_query.all()
        # 4. 最终排序（必须在过滤后执行，否则可能失效）
        return queryset.order_by('-update_at')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # 额外传递分类信息
        code = 'base.BaseModel'
        context['reports'] = baseModels.ReportFileModel.objects.filter(code=code, type='1', state='1').order_by('-default').all()
        context['report_code'] = code
        context['report_type'] = '1'
        context['total_count'] = self.get_queryset().count()  # 总条数
        return context
class WlAddForm(forms.Form):
    STATE_CHOICES = [
        ("1", "启用"),
        ("0", "停用"),
    ]
    # 示例字段：根据你的需求定义字段
    name = forms.CharField(max_length=50, label='品名', initial='')
    jian_pin = forms.CharField(max_length=50, label='简拼', initial='',required=False)
    quan_pin = forms.CharField(max_length=50, label='全拼', initial='',required=False)
    gui_ge = forms.CharField(max_length=50, label='默认规格', initial='',required=False)
    code = forms.CharField(max_length=50, label='代码', initial='')
    state = forms.ChoiceField(label='状态', initial='1', choices=STATE_CHOICES)
    version = forms.IntegerField(label='版本', initial=1)
    chan_di = forms.CharField(max_length=50, label='产地', initial='',required=False)
    remark = forms.CharField(max_length=255,
                              label='备注', initial='',required=False ) # 核心参数：允许为空)
    # 新增物料类型单选框字段
    MATERIAL_TYPE_CHOICES = [
        ("raw", "原料"),
        ("additive", "添加剂"),
        ("packaging", "包材"),  # 后续处理禁用逻辑
        ("intermediate", "半成品"),  # 后续处理禁用逻辑
        ("finished", "成品"),  # 后续处理禁用逻辑
    ]
    material_type = forms.ChoiceField(
        label="物料类型",
        choices=MATERIAL_TYPE_CHOICES,
        widget=forms.RadioSelect(attrs={'class': 'form-check-input'}),
        required=True  # 设为必填项，根据需求调整
    )
    TEMP_CHOICES = [
        ("1", "常温"),
        ("0", "阴凉"),
    ]
    storage_condition = forms.ChoiceField(label='贮存条件', initial='1',
                                          widget=forms.Select(attrs={'class': 'form-select'}) ,
                        choices=TEMP_CHOICES)

    # 将原来的standard字符字段改为外键关联到StandardModel
    standard = forms.ModelChoiceField(
        queryset=baseModels.StandardModel.objects.all(),  # 数据源：关联模型的所有数据
        label='执行标准',
        widget=forms.Select(attrs={'class': 'form-select'}),
        required=True,  # 表单层面必填
        empty_label=None  # 不显示空选项（可选）
    # # 自定义下拉框选项文本（例如显示标准编号+名称）
    # label_from_instance = lambda obj: f"{obj.code} - {obj.name}"
    )



class WlAddEditDelView(LoginRequiredMixin,View):
    """物料添加视图：处理原料、添加剂、包装材料的添加逻辑"""
    template_name = 'base/add.html'  # 模板路径集中管理
    success_url = 'wl_list'  # 成功后重定向的URL名称（需在urls.py中定义）

    # 物料类型与模型的映射关系（替代冗长的if-elif）
    MODEL_MAP = {
        'raw': baseModels.RawModel,
        'additive': baseModels.AdditiveModel,
        'packaging': baseModels.PackagingModel,
        'intermediate': baseModels.IntermediateModel,
        'finished': baseModels.FinishedModel,
    }


    def get(self, request):
        """展示空表单"""
        pk=request.GET.get('pk')
        material_type=request.GET.get('material_type')  # 从URL获取模型类型（raw/additive/packaging）
        if pk and material_type:
            # 编辑场景：1. 获取对应模型类 2. 查询实例 3. 提取字段值作为initial
            model_class = self.MODEL_MAP.get(material_type)
            if not model_class:
                messages.error(request, "无效的物料类型")
                return redirect(self.success_url)
            # 获取模型实例（不存在返回404）
            instance = get_object_or_404(model_class, pk=pk)

            # 构造initial数据：表单字段名与模型字段名必须一致
            initial_data = {
                'name': instance.name,
                'jian_pin': instance.jian_pin,
                'quan_pin': instance.quan_pin,
                'gui_ge': instance.gui_ge,
                'code': instance.code,
                'state': instance.state,
                'version': instance.version,
                'chan_di': instance.chan_di,
                'remark': instance.remark,
                'material_type': material_type,  # 回显物料类型单选框
                'storage_condition': instance.storage_condition,
                'standard': instance.standard  # 外键字段直接传实例
            }

            # # 传入initial数据，表单自动回显
            form = WlAddForm(initial=initial_data)

        else:
            # 新增场景：空表单（若URL带model_type，可默认选中该类型）
            initial = {'material_type': material_type} if material_type else {}
            form = WlAddForm(initial=initial)
        code = 'base.BaseModel'
        reports = baseModels.ReportFileModel.objects.filter(code=code, type='0', state='1').order_by(
            '-default').all()
        context={'form': form,'pk':pk,'reports':reports,'report_code':code,
                 'report_type': '0', }
        return render(request, self.template_name,context)

    def post(self, request):
        """处理表单提交"""
        _method = request.POST.get('_method')
        _methods = request.POST.get('_methods')
        if _methods=='DELETE':
            delete_ids = request.POST.getlist('delete_ids')  # 隐藏字段传递pk
            return self.delete(request,delete_ids)
        elif 'DELETE' == _method:
            return self.delete(request)

        form = WlAddForm(request.POST)
        if form.is_valid():
            try:
                # 提取物料类型并获取对应的模型
                material_type = form.cleaned_data.pop('material_type')
                pk = request.POST.get('pk')  # 隐藏字段传递pk
                model = self.MODEL_MAP.get(material_type)

                if not model:
                    # 处理未知物料类型的情况
                    form.add_error('material_type', '无效的物料类型')
                    return render(request, self.template_name, {'form': form})
                # 编辑场景：获取实例；新增场景：无实例
                instance = get_object_or_404(model, pk=pk) if pk else None
                if not instance:

                    # 保存数据到数据库
                    model.objects.create(**form.cleaned_data)
                    # 添加成功提示
                    messages.success(request, '物料添加成功！')
                else:
                    # 编辑：更新实例字段
                    for key, value in  form.cleaned_data.items():
                        setattr(instance, key, value)
                    instance.save()
                    messages.success(request, "修改成功")
                return redirect(self.success_url)

            except Exception as e:
                # 捕获数据库操作等异常
                messages.error(request, f'添加失败：{str(e)}')
                return render(request, self.template_name, {'form': form})

        # 表单验证失败时返回错误信息
        return render(request, self.template_name, {'form': form})

    def delete(self, request,delete_ids=[]):
        """处理DELETE请求"""
        if not delete_ids:
            material_type = request.POST.get('material_type')
            pk = request.POST.get('pk')  # 隐藏字段传递pk

            # 1. 验证模型类型
            model_class = self.MODEL_MAP.get(material_type)
            if not model_class:
                messages.error(request, "无效的物料类型")
                return redirect(self.success_url)

            # 2. 查询实例（不存在返回404）
            instance = get_object_or_404(model_class, pk=pk)

            # 3. 执行删除
            try:
                instance.delete()
                messages.success(request, f"【{instance.name}】已成功删除")
            except Exception as e:
                messages.error(request, f"删除失败：{str(e)}")
        else:
            # 使用事务确保所有操作的原子性
            with transaction.atomic():
                instances=[]
                for delete_id in delete_ids:
                    pk,material_type = delete_id.split('-')
                    # 1. 验证模型类型
                    model_class = self.MODEL_MAP.get(material_type)
                    if not model_class:
                        messages.error(request, "无效的物料类型")
                        return redirect(self.success_url)
                    # 2. 查询实例（不存在返回404）
                    instance = get_object_or_404(model_class, pk=pk)
                    instances.append(instance)
                # 3. 执行删除
                for instance in instances:
                    try:
                        instance.delete()
                    except Exception as e:
                        messages.error(request, f"删除失败：{str(e)}")
                messages.success(request, f"【{len(instances)}条数据】已成功删除")
        # 4. 重定向回列表页
        return redirect(self.success_url)



class SuppliersView(LoginRequiredMixin,ListView):
    """
    HomeView for website views.
    ListView 参数
    model 指定模型类（如 model = Book），自动查询该模型的所有数据
    queryset 自定义查询集（如 queryset = Book.objects.filter(price__gt=50)），优先级高于 model
    ordering 指定默认排序方式（如 ordering = ['-publish_date'] 或 ordering = '-price'）
    paginate_by 分页大小（如 paginate_by = 10 表示每页显示 10 条）
    paginate_orphans 最后一页允许的最小剩余条数（如 paginate_orphans = 3，避免最后一页只有 1-2 条数据）
    allow_empty 是否允许空列表（默认 True，空列表时正常显示；设为 False 会返回 404）
    template_name 自定义模板路径（如 template_name = 'myapp/book_list.html'）
    template_name_suffix 模板名后缀（默认 _list，结合模型名自动生成模板名，如 book_list.html）
    context_object_name 模板中使用的列表变量名（默认是模型名小写加 _list，如 book_list；可自定义为 books）
    get_context_data()	 重写该方法添加额外上下文（如传递分类、统计数据等）
    def get_context_data(self, **kwargs):
        context = super().get_context_data(** kwargs)
        context['category'] = "小说"  # 额外传递分类信息
        context['total_count'] = self.get_queryset().count()  # 总条数
        return context
    page_kwarg 分页参数名（默认 page，如 URL 中的 ?page=2；可改为 p 即 ?p=2）
    paginator_class 自定义分页器类（默认 django.core.paginator.Paginator）
    http_method_names 允许的 HTTP 方法（默认 ['get', 'head', 'options']，通常无需修改）
    extra_context 静态额外上下文（字典形式，如 extra_context = {'title': '书籍列表'}）

    """

    @classmethod
    def get_detail_data(cls, request):
        detail_data = []

        search_q = request.POST.get('q', '').strip()
        # 状态筛选（select_type）：默认 None（不过滤）
        select_type = request.POST.get('select_type')  # 启用，停用

        material_type = request.POST.get('material_type_search', 'all')
        # 物料类型与模型的映射关系（替代冗长的if-elif）
        MODEL_MAP = {
            'raw': baseModels.SupplierRawModel,
            'additive': baseModels.SupplierAdditiveModel,
            'packaging': baseModels.SupplierPackagingModel,

        }
        if material_type == 'all':
            raw_query = MODEL_MAP.get('raw').objects.annotate(
                wl=Value('raw', output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type).all()
            additive_query = MODEL_MAP.get('additive').objects.annotate(
                wl=Value('additive', output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type).all()
            packaging_query = MODEL_MAP.get('packaging').objects.annotate(
                wl=Value('packaging', output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type).all()


            # 3. 根据参数过滤查询集
            # 注意：union 后的查询集是 "联合查询集"，需先转为列表再过滤，或使用 Q 对象（视模型字段兼容性而定）

            # 重新合并过滤后的查询集
            queryset = raw_query.union(additive_query, all=True).union(packaging_query, all=True
                                                                       )
        else:
            queryset = MODEL_MAP.get(material_type).objects.annotate(
                wl=Value(material_type, output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type)

        # 4. 最终排序（必须在过滤后执行，否则可能失效）
        # # 获取数据并转换为字典
        items = queryset.order_by('-create_at').all()
        for item in items:
            item_dict = model_to_dict(item)
            # 关键：添加注解的 wl 字段（item.wl 是 annotate 生成的值）
            item_dict['wl'] = item.wl  # 新增这一行
            item_dict['raws']=','.join([ raw.name for raw in  item_dict['raws'] ])
            # 处理日期时间类型（转换为字符串）
            for key, value in item_dict.items():
                if isinstance(value, timezone.datetime):
                    item_dict[key] = value.strftime('%Y-%m-%d %H:%M:%S')
                elif isinstance(value, datetime.date):
                    item_dict[key] = value.strftime('%Y-%m-%d')
            detail_data.append(item_dict)
        return detail_data

    # model = baseModels.RawModel
    # ordering = ['id']  # 必加：指定默认排序（避免 Django 警告，也能解决部分元数据初始化问题）
    context_object_name = 'wl_list'  # 可选：自定义模板变量名
    paginate_by =8  # 可选：分页
    template_name='base/suppliermodel_list.html'
    def get_queryset(self):
        # 2. 获取前端提交的搜索参数（从 self.request.GET 中提取）
        # 搜索关键词（q）：默认空字符串
        search_q = self.request.GET.get('q', '').strip()
        # 状态筛选（select_type）：默认 None（不过滤）
        select_type = self.request.GET.get('select_type','1')
        material_type = self.request.GET.get('material_type_search','all')
        # 物料类型与模型的映射关系（替代冗长的if-elif）
        MODEL_MAP = {
            'raw': baseModels.SupplierRawModel,
            'additive': baseModels.SupplierAdditiveModel,
            'packaging': baseModels.SupplierPackagingModel,

        }

        # 1. 定义查询公共逻辑（避免重复代码）
        def get_model_query(model, material_type, search_q, select_type):
            return model.objects.annotate(
                wl=Value(material_type, output_field=CharField())  # 注解为当前物料类型
            ).filter(
                name__icontains=search_q,
                state__icontains=select_type
            ).all()

        # 3. 分情况查询并合并结果
        queryset = []
        if material_type == 'all':
            # 合并所有模型的查询结果（Python 列表合并，保留原始模型）
            for m_type, model in MODEL_MAP.items():
                queryset.extend(get_model_query(model, m_type, search_q, select_type))
        else:
            # 只查询指定模型
            model = MODEL_MAP.get(material_type)
            if model:
                queryset = get_model_query(model, material_type, search_q, select_type)

        # 后续可对 queryset 进行排序、切片等操作（注意：此时是列表，需用 Python 方法）
        # 示例：按名称排序
        queryset.sort(key=lambda x: x.create_at,reverse=True)
        # 4. 最终排序（必须在过滤后执行，否则可能失效）
        return queryset

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # 额外传递分类信息
        code = 'base.SupplierModel'
        context['reports'] = baseModels.ReportFileModel.objects.filter(code=code, type='1', state='1').order_by('-default').all()
        context['report_code'] = code
        context['report_type'] = '1'
        context['total_count'] = len(self.get_queryset())  # 总条数
        return context

from .widgets import HorizontalFilterWidget
class SupplierForm(forms.Form):
    STATE_CHOICES = [
        ("1", "启用"),
        ("0", "停用"),
    ]

    # 基础字段（保持原有逻辑，优化默认值和提示）
    name = forms.CharField(
        max_length=50,
        label='品名',
        initial='',
        widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入品名'})
    )
    jian_pin = forms.CharField(
        max_length=50,
        label='简拼',
        initial='',
        required=False,
        widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '可选，自动生成或手动输入'})
    )
    quan_pin = forms.CharField(
        max_length=50,
        label='全拼',
        initial='',
        required=False,
        widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '可选，自动生成或手动输入'})
    )

    state = forms.ChoiceField(
        label='状态',
        initial='1',
        choices=STATE_CHOICES,
        widget=forms.Select(attrs={'class': 'form-select'})
    )
    exp_date = forms.DateField(
        label='有效日期',
        initial=timezone.now().date(),  # 修正：默认值取日期（不含时间），避免前端格式问题
        widget=forms.DateInput(
            attrs={'class': 'form-control', 'type': 'date'},  # 用原生日期选择器，更友好
            format='%Y-%m-%d'  # 日期格式统一
        ),
        input_formats=['%Y-%m-%d']  # 允许的输入格式
    )

    remark = forms.CharField(
        max_length=255,
        label='备注',
        initial='',
        required=False,
        widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': '可选，输入备注信息'})
    )

    # 物料类型单选框（新增禁用"包材"的逻辑）
    MATERIAL_TYPE_CHOICES = [
        ("raw", "原料"),
        ("additive", "添加剂"),
        ("packaging", "包材"),  # 需要禁用的选项
    ]
    material_type = forms.ChoiceField(
        label="供应物料类型",
        choices=MATERIAL_TYPE_CHOICES,
        widget=forms.RadioSelect(
            attrs={
                'class': 'form-check-input d-flex gap-4 align-items-center form-check-inline',  # 横向布局核心类
                # 补充：每个选项的父容器样式（通过 widget 的 template_name 或直接加类）
            }
        ),
        required=True
    )

    # 修正：Form 中用 MultipleChoiceField 替代 ManyToManyField（关联原料选择）
    # raws = forms.MultipleChoiceField(
    #     label="关联原料",
    #     required=False,  # 根据需求调整是否必填
    #     widget=forms.SelectMultiple(attrs={'class':'form-select'}),
    #     # choices=[(1,'a'),(2,'cafasfsaf')],  # 初始为空，在 __init__ 中动态加载原料数据
    #     # initial=[1,]
    # )
    # 修正：Form 中用 MultipleChoiceField 替代 ManyToManyField（关联原料选择）
    raws = forms.MultipleChoiceField(
        label="关联原料",
        required=False,  # 根据需求调整是否必填
        widget=HorizontalFilterWidget(attrs={'class':'form-select'}),
        # choices=[(1,'a'),(2,'cafasfsaf')],  # 初始为空，在 __init__ 中动态加载原料数据
        # initial=[1,]
    )

    def __init__(self, *args, **kwargs):
        # 接收并移除自定义参数（如果需要）
        # 比如当前用户、特定条件等
        material_type = kwargs.get('initial',{}).get('material_type','all')
        name = kwargs.get('initial',{}).get('name', '')
        print('material_type',material_type)
        super().__init__(*args, **kwargs)
        # 物料类型单选框（新增禁用"包材"的逻辑）
        self.MATERIAL_TYPE_Models = {
            "raw": baseModels.RawModel,
            "additive":baseModels.AdditiveModel,
            "packaging": baseModels.PackagingModel,  # 需要禁用的选项
            "all":baseModels.RawModel,
        }
        # 动态加载原料选项
        # 从数据库查询所有原料，按名称排序
        raw_materials = self.MATERIAL_TYPE_Models.get(material_type).objects.all().order_by('name')

        # 转换为 (value, label) 格式的元组列表
        # value 通常是模型的主键 id
        self.fields['raws'].choices = [
            (raw.id, raw.name) for raw in raw_materials
        ]

        # 如果需要设置动态初始值，可以在这里处理
        # 例如：默认选中第一个原料
        if raw_materials.exists() and name :
            raws= self.MATERIAL_TYPE_Models.get(material_type).objects.filter(suppliers__name=name)
            self.fields['raws'].initial = [ raw.id for raw in raws.all()]
class SupplierView(LoginRequiredMixin,View):
    """物料添加视图：处理原料、添加剂、包装材料的添加逻辑"""
    template_name = 'base/supplier.html'  # 模板路径集中管理
    success_url = 'supplier_list'  # 成功后重定向的URL名称（需在urls.py中定义）

    # 物料类型与模型的映射关系（替代冗长的if-elif）
    MODEL_MAP = {
        'raw': baseModels.SupplierRawModel,
        'additive': baseModels.SupplierAdditiveModel,
        'packaging': baseModels.SupplierPackagingModel,

    }


    def get(self, request):
        """展示空表单"""
        material_raws_map=[]
        pk=request.GET.get('pk')
        material_type=request.GET.get('material_type')  # 从URL获取模型类型（raw/additive/packaging）
        if pk and material_type:
            # 编辑场景：1. 获取对应模型类 2. 查询实例 3. 提取字段值作为initial
            model_class = self.MODEL_MAP.get(material_type)
            if not model_class:
                messages.error(request, "无效的物料类型")
                return redirect(self.success_url)
            # 获取模型实例（不存在返回404）
            instance = get_object_or_404(model_class, pk=pk)

            # 构造initial数据：表单字段名与模型字段名必须一致
            initial_data = {
                'name': instance.name,
                'jian_pin': instance.jian_pin,
                'quan_pin': instance.quan_pin,
                'state': instance.state,
                'exp_date':str(instance.exp_date),
                'remark': instance.remark,
                'material_type': material_type,  # 回显物料类型单选框
                # 'raws': instance.raws,  # 多选项回显：传递ID列表
            }

            # # 传入initial数据，表单自动回显
            form = SupplierForm(initial=initial_data)

        else:
            # 新增场景：空表单（若URL带model_type，可默认选中该类型）
            initial = {'material_type': material_type} if material_type else {}
            form = SupplierForm(initial=initial)
            material_raws_map = {'raw':[[item.id,item.name] for item in baseModels.RawModel.objects.filter(state='1').all()],
                                 'additive':[[item.id,item.name] for item in baseModels.AdditiveModel.objects.filter(state='1').all()],
                                 'packaging': [[item.id, item.name] for item in
                                              baseModels.PackagingModel.objects.filter(state='1').all()],
                                 }
        code = 'base.SupplierModel'
        reports = baseModels.ReportFileModel.objects.filter(code=code, type='0', state='1').order_by(
            '-default').all()

        context={'form': form,'pk':pk,'reports':reports,'report_code':code,
                 'report_type': '0', 'material_raws_map':material_raws_map}
        return render(request, self.template_name,context)

    def post(self, request):
        """处理表单提交"""
        _method = request.POST.get('_method')
        _methods = request.POST.get('_methods')
        if _methods=='DELETE':
            delete_ids = request.POST.getlist('delete_ids')  # 隐藏字段传递pk
            return self.delete(request,delete_ids)
        elif 'DELETE' == _method:
            return self.delete(request)

        form = SupplierForm(request.POST)
        if form.is_valid():
            try:
                # 提取物料类型并获取对应的模型
                material_type = form.cleaned_data.pop('material_type')
                pk = request.POST.get('pk')  # 隐藏字段传递pk
                model = self.MODEL_MAP.get(material_type)
                raws = form.cleaned_data.pop('raws')
                if not model:
                    # 处理未知物料类型的情况
                    form.add_error('material_type', '无效的物料类型')
                    return render(request, self.template_name, {'form': form})
                # 编辑场景：获取实例；新增场景：无实例
                instance = get_object_or_404(model, pk=pk) if pk else None
                if not instance:

                    # 保存数据到数据库
                    instance = model.objects.create(**form.cleaned_data)
                    getattr(instance, 'raws').set(raws)
                    # 添加成功提示
                    messages.success(request, '物料添加成功！')
                else:
                    # 编辑：更新实例字段
                    for key, value in  form.cleaned_data.items():
                        setattr(instance, key, value)
                    getattr(instance, 'raws').set(raws)
                    instance.save()
                    messages.success(request, "修改成功")
                return redirect(self.success_url)

            except Exception as e:
                # 捕获数据库操作等异常
                messages.error(request, f'添加失败：{str(e)}')
                return render(request, self.template_name, {'form': form})

        # 表单验证失败时返回错误信息
        return render(request, self.template_name, {'form': form})

    def delete(self, request,delete_ids=[]):
        """处理DELETE请求"""
        if not delete_ids:
            material_type = request.POST.get('material_type')
            pk = request.POST.get('pk')  # 隐藏字段传递pk

            # 1. 验证模型类型
            model_class = self.MODEL_MAP.get(material_type)
            if not model_class:
                messages.error(request, "无效的物料类型")
                return redirect(self.success_url)

            # 2. 查询实例（不存在返回404）
            instance = get_object_or_404(model_class, pk=pk)

            # 3. 执行删除
            try:
                instance.raws.clear()
                instance.delete()
                messages.success(request, f"【{instance.name}】已成功删除")
            except Exception as e:
                messages.error(request, f"删除失败：{str(e)}")
        else:
            # 使用事务确保所有操作的原子性
            with transaction.atomic():
                instances=[]
                for delete_id in delete_ids:
                    pk,material_type = delete_id.split('-')
                    # 1. 验证模型类型
                    model_class = self.MODEL_MAP.get(material_type)
                    if not model_class:
                        messages.error(request, "无效的物料类型")
                        return redirect(self.success_url)
                    # 2. 查询实例（不存在返回404）
                    instance = get_object_or_404(model_class, pk=pk)

                    instances.append(instance)
                # 3. 执行删除
                for instance in instances:
                    try:
                        instance.raws.clear()
                        instance.delete()
                    except Exception as e:
                        messages.error(request, f"删除失败：{str(e)}")
                messages.success(request, f"【{len(instances)}条数据】已成功删除")
        # 4. 重定向回列表页
        return redirect(self.success_url)

class CustomersView(LoginRequiredMixin,ListView):
    """
    HomeView for website views.
    ListView 参数
    model 指定模型类（如 model = Book），自动查询该模型的所有数据
    queryset 自定义查询集（如 queryset = Book.objects.filter(price__gt=50)），优先级高于 model
    ordering 指定默认排序方式（如 ordering = ['-publish_date'] 或 ordering = '-price'）
    paginate_by 分页大小（如 paginate_by = 10 表示每页显示 10 条）
    paginate_orphans 最后一页允许的最小剩余条数（如 paginate_orphans = 3，避免最后一页只有 1-2 条数据）
    allow_empty 是否允许空列表（默认 True，空列表时正常显示；设为 False 会返回 404）
    template_name 自定义模板路径（如 template_name = 'myapp/book_list.html'）
    template_name_suffix 模板名后缀（默认 _list，结合模型名自动生成模板名，如 book_list.html）
    context_object_name 模板中使用的列表变量名（默认是模型名小写加 _list，如 book_list；可自定义为 books）
    get_context_data()	 重写该方法添加额外上下文（如传递分类、统计数据等）
    def get_context_data(self, **kwargs):
        context = super().get_context_data(** kwargs)
        context['category'] = "小说"  # 额外传递分类信息
        context['total_count'] = self.get_queryset().count()  # 总条数
        return context
    page_kwarg 分页参数名（默认 page，如 URL 中的 ?page=2；可改为 p 即 ?p=2）
    paginator_class 自定义分页器类（默认 django.core.paginator.Paginator）
    http_method_names 允许的 HTTP 方法（默认 ['get', 'head', 'options']，通常无需修改）
    extra_context 静态额外上下文（字典形式，如 extra_context = {'title': '书籍列表'}）

    """

    @classmethod
    def get_detail_data(cls, request):
        detail_data = []

        search_q = request.POST.get('q', '').strip()
        # 状态筛选（select_type）：默认 None（不过滤）
        select_type = request.POST.get('select_type')  # 启用，停用

        material_type = request.POST.get('material_type_search', 'all')
        # 物料类型与模型的映射关系（替代冗长的if-elif）
        MODEL_MAP = {
            'raw': baseModels.CustomerRawModel,
            'additive': baseModels.CustomerAdditiveModel,
            'packaging': baseModels.CustomerPackagingModel,
            'intermediate': baseModels.CustomerIntermediateModel,
            'finished': baseModels.CustomerFinishedModel,

        }
        if material_type == 'all':
            raw_query = MODEL_MAP.get('raw').objects.annotate(
                wl=Value('raw', output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type).all()
            additive_query = MODEL_MAP.get('additive').objects.annotate(
                wl=Value('additive', output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type).all()
            packaging_query = MODEL_MAP.get('packaging').objects.annotate(
                wl=Value('packaging', output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type).all()
            intermediate_query = MODEL_MAP.get('intermediate').objects.annotate(
                wl=Value('intermediate', output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type).all()
            finished_query = MODEL_MAP.get('finished').objects.annotate(
                wl=Value('finished', output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type).all()
            # 3. 根据参数过滤查询集
            # 注意：union 后的查询集是 "联合查询集"，需先转为列表再过滤，或使用 Q 对象（视模型字段兼容性而定）

            # 重新合并过滤后的查询集
            queryset = raw_query.union(additive_query, all=True).union(packaging_query, all=True
                    ).union(intermediate_query, all=True).union(finished_query, all=True )
        else:
            queryset = MODEL_MAP.get(material_type).objects.annotate(
                wl=Value(material_type, output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type)

        # 4. 最终排序（必须在过滤后执行，否则可能失效）
        # # 获取数据并转换为字典
        items = queryset.order_by('-create_at').all()
        for item in items:
            item_dict = model_to_dict(item)
            # 关键：添加注解的 wl 字段（item.wl 是 annotate 生成的值）
            item_dict['wl'] = item.wl  # 新增这一行
            item_dict['raws']=','.join([ raw.name for raw in  item_dict['raws'] ])
            # 处理日期时间类型（转换为字符串）
            for key, value in item_dict.items():
                if isinstance(value, timezone.datetime):
                    item_dict[key] = value.strftime('%Y-%m-%d %H:%M:%S')
                elif isinstance(value, datetime.date):
                    item_dict[key] = value.strftime('%Y-%m-%d')
            detail_data.append(item_dict)
        return detail_data

    # model = baseModels.RawModel
    # ordering = ['id']  # 必加：指定默认排序（避免 Django 警告，也能解决部分元数据初始化问题）
    context_object_name = 'wl_list'  # 可选：自定义模板变量名
    paginate_by =8  # 可选：分页
    template_name='base/customermodel_list.html'
    def get_queryset(self):
        # 2. 获取前端提交的搜索参数（从 self.request.GET 中提取）
        # 搜索关键词（q）：默认空字符串
        search_q = self.request.GET.get('q', '').strip()
        # 状态筛选（select_type）：默认 None（不过滤）
        select_type = self.request.GET.get('select_type','1')
        material_type = self.request.GET.get('material_type_search','all')
        # 物料类型与模型的映射关系（替代冗长的if-elif）
        MODEL_MAP = {
            'raw': baseModels.CustomerRawModel,
            'additive': baseModels.CustomerAdditiveModel,
            'packaging': baseModels.CustomerPackagingModel,
            'intermdeiate': baseModels.CustomerIntermediateModel,
            'finished': baseModels.CustomerFinishedModel,

        }

        # 1. 定义查询公共逻辑（避免重复代码）
        def get_model_query(model, material_type, search_q, select_type):
            return model.objects.annotate(
                wl=Value(material_type, output_field=CharField())  # 注解为当前物料类型
            ).filter(
                name__icontains=search_q,
                state__icontains=select_type
            ).all()

        # 3. 分情况查询并合并结果
        queryset = []
        if material_type == 'all':
            # 合并所有模型的查询结果（Python 列表合并，保留原始模型）
            for m_type, model in MODEL_MAP.items():
                queryset.extend(get_model_query(model, m_type, search_q, select_type))
        else:
            # 只查询指定模型
            model = MODEL_MAP.get(material_type)
            if model:
                queryset = get_model_query(model, material_type, search_q, select_type)

        # 后续可对 queryset 进行排序、切片等操作（注意：此时是列表，需用 Python 方法）
        # 示例：按名称排序
        queryset.sort(key=lambda x: x.create_at,reverse=True)
        # 4. 最终排序（必须在过滤后执行，否则可能失效）
        return queryset

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # 额外传递分类信息
        code = 'base.CustomerModel'
        context['reports'] = baseModels.ReportFileModel.objects.filter(code=code, type='1', state='1').order_by('-default').all()
        context['report_code'] = code
        context['report_type'] = '1'
        context['total_count'] = len(self.get_queryset())  # 总条数
        return context

class CustomerForm(forms.Form):
    STATE_CHOICES = [
        ("1", "启用"),
        ("0", "停用"),
    ]

    # 基础字段（保持原有逻辑，优化默认值和提示）
    name = forms.CharField(
        max_length=50,
        label='品名',
        initial='',
        widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入品名'})
    )
    jian_pin = forms.CharField(
        max_length=50,
        label='简拼',
        initial='',
        required=False,
        widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '可选，自动生成或手动输入'})
    )
    quan_pin = forms.CharField(
        max_length=50,
        label='全拼',
        initial='',
        required=False,
        widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '可选，自动生成或手动输入'})
    )

    state = forms.ChoiceField(
        label='状态',
        initial='1',
        choices=STATE_CHOICES,
        widget=forms.Select(attrs={'class': 'form-select'})
    )
    exp_date = forms.DateField(
        label='有效日期',
        initial=timezone.now().date(),  # 修正：默认值取日期（不含时间），避免前端格式问题
        widget=forms.DateInput(
            attrs={'class': 'form-control', 'type': 'date'},  # 用原生日期选择器，更友好
            format='%Y-%m-%d'  # 日期格式统一
        ),
        input_formats=['%Y-%m-%d']  # 允许的输入格式
    )

    remark = forms.CharField(
        max_length=255,
        label='备注',
        initial='',
        required=False,
        widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': '可选，输入备注信息'})
    )

    # 物料类型单选框（新增禁用"包材"的逻辑）
    MATERIAL_TYPE_CHOICES = [
        ("raw", "原料"),
        ("additive", "添加剂"),
        ("packaging", "包材"),  # 需要禁用的选项
        ("intermediate", "中间品"),  # 需要禁用的选项
        ("finished", "成品"),  # 需要禁用的选项
    ]
    material_type = forms.ChoiceField(
        label="供应物料类型",
        choices=MATERIAL_TYPE_CHOICES,
        widget=forms.RadioSelect(
            attrs={
                'class': 'form-check-input d-flex gap-4 align-items-center form-check-inline',  # 横向布局核心类
                # 补充：每个选项的父容器样式（通过 widget 的 template_name 或直接加类）
            }
        ),
        required=True
    )

    # 修正：Form 中用 MultipleChoiceField 替代 ManyToManyField（关联原料选择）
    # raws = forms.MultipleChoiceField(
    #     label="关联原料",
    #     required=False,  # 根据需求调整是否必填
    #     widget=forms.SelectMultiple(attrs={'class':'form-select'}),
    #     # choices=[(1,'a'),(2,'cafasfsaf')],  # 初始为空，在 __init__ 中动态加载原料数据
    #     # initial=[1,]
    # )
    # 修正：Form 中用 MultipleChoiceField 替代 ManyToManyField（关联原料选择）
    raws = forms.MultipleChoiceField(
        label="关联物料",
        required=False,  # 根据需求调整是否必填
        widget=HorizontalFilterWidget(attrs={'class':'form-select'}),
        # choices=[(1,'a'),(2,'cafasfsaf')],  # 初始为空，在 __init__ 中动态加载原料数据
        # initial=[1,]
    )

    def __init__(self, *args, **kwargs):
        # 接收并移除自定义参数（如果需要）
        # 比如当前用户、特定条件等
        material_type = kwargs.get('initial',{}).get('material_type','all')
        name = kwargs.get('initial',{}).get('name', '')
        print('material_type',material_type)
        super().__init__(*args, **kwargs)
        # 物料类型单选框（新增禁用"包材"的逻辑）
        self.MATERIAL_TYPE_Models = {
            "raw": baseModels.RawModel,
            "additive":baseModels.AdditiveModel,
            "packaging": baseModels.PackagingModel,  # 需要禁用的选项
            "intermediate": baseModels.IntermediateModel,  # 需要禁用的选项
            "finished": baseModels.FinishedModel,  # 需要禁用的选项
            "all":baseModels.RawModel,
        }
        # 动态加载原料选项
        # 从数据库查询所有原料，按名称排序
        raw_materials = self.MATERIAL_TYPE_Models.get(material_type).objects.all().order_by('name')

        # 转换为 (value, label) 格式的元组列表
        # value 通常是模型的主键 id
        self.fields['raws'].choices = [
            (raw.id, raw.name) for raw in raw_materials
        ]

        # 如果需要设置动态初始值，可以在这里处理
        # 例如：默认选中第一个原料
        if raw_materials.exists() and name :
            raws= self.MATERIAL_TYPE_Models.get(material_type).objects.filter(customers__name=name)
            self.fields['raws'].initial = [ raw.id for raw in raws.all()]
class CustomerView(LoginRequiredMixin,View):
    """物料添加视图：处理原料、添加剂、包装材料的添加逻辑"""
    template_name = 'base/customer.html'  # 模板路径集中管理
    success_url = 'customer_list'  # 成功后重定向的URL名称（需在urls.py中定义）

    # 物料类型与模型的映射关系（替代冗长的if-elif）
    MODEL_MAP = {
        'raw': baseModels.CustomerRawModel,
        'additive': baseModels.CustomerAdditiveModel,
        'packaging': baseModels.CustomerPackagingModel,
        'intermediate': baseModels.CustomerIntermediateModel,
        'finished': baseModels.CustomerFinishedModel,

    }


    def get(self, request):
        """展示空表单"""
        material_raws_map=[]
        pk=request.GET.get('pk')
        material_type=request.GET.get('material_type')  # 从URL获取模型类型（raw/additive/packaging）
        if pk and material_type:
            # 编辑场景：1. 获取对应模型类 2. 查询实例 3. 提取字段值作为initial
            model_class = self.MODEL_MAP.get(material_type)
            if not model_class:
                messages.error(request, "无效的物料类型")
                return redirect(self.success_url)
            # 获取模型实例（不存在返回404）
            instance = get_object_or_404(model_class, pk=pk)

            # 构造initial数据：表单字段名与模型字段名必须一致
            initial_data = {
                'name': instance.name,
                'jian_pin': instance.jian_pin,
                'quan_pin': instance.quan_pin,
                'state': instance.state,
                'exp_date':str(instance.exp_date),
                'remark': instance.remark,
                'material_type': material_type,  # 回显物料类型单选框
                # 'raws': instance.raws,  # 多选项回显：传递ID列表
            }

            # # 传入initial数据，表单自动回显
            form = CustomerForm(initial=initial_data)

        else:
            # 新增场景：空表单（若URL带model_type，可默认选中该类型）
            initial = {'material_type': material_type} if material_type else {}
            form = CustomerForm(initial=initial)
            material_raws_map = {'raw':[[item.id,item.name] for item in baseModels.RawModel.objects.filter(state='1').all()],
                                 'additive':[[item.id,item.name] for item in baseModels.AdditiveModel.objects.filter(state='1').all()],
                                 'packaging': [[item.id, item.name] for item in
                                              baseModels.PackagingModel.objects.filter(state='1').all()],
                                 'intermediate': [[item.id, item.name] for item in
                                               baseModels.IntermediateModel.objects.filter(state='1').all()],
                                 'finished': [[item.id, item.name] for item in
                                               baseModels.FinishedModel.objects.filter(state='1').all()],
                                 }
        code = 'base.CustomerModel'
        reports = baseModels.ReportFileModel.objects.filter(code=code, type='0', state='1').order_by(
            '-default').all()

        context={'form': form,'pk':pk,'reports':reports,'report_code':code,
                 'report_type': '0', 'material_raws_map':material_raws_map}
        return render(request, self.template_name,context)

    def post(self, request):
        """处理表单提交"""
        _method = request.POST.get('_method')
        _methods = request.POST.get('_methods')
        if _methods=='DELETE':
            delete_ids = request.POST.getlist('delete_ids')  # 隐藏字段传递pk
            return self.delete(request,delete_ids)
        elif 'DELETE' == _method:
            return self.delete(request)

        form = CustomerForm(request.POST)
        if form.is_valid():
            try:
                # 提取物料类型并获取对应的模型
                material_type = form.cleaned_data.pop('material_type')
                pk = request.POST.get('pk')  # 隐藏字段传递pk
                model = self.MODEL_MAP.get(material_type)
                raws = form.cleaned_data.pop('raws')
                if not model:
                    # 处理未知物料类型的情况
                    form.add_error('material_type', '无效的物料类型')
                    return render(request, self.template_name, {'form': form})
                # 编辑场景：获取实例；新增场景：无实例
                instance = get_object_or_404(model, pk=pk) if pk else None
                if not instance:

                    # 保存数据到数据库
                    instance = model.objects.create(**form.cleaned_data)
                    getattr(instance, 'raws').set(raws)
                    # 添加成功提示
                    messages.success(request, '物料添加成功！')
                else:
                    # 编辑：更新实例字段
                    for key, value in  form.cleaned_data.items():
                        setattr(instance, key, value)
                    getattr(instance, 'raws').set(raws)
                    instance.save()
                    messages.success(request, "修改成功")
                return redirect(self.success_url)

            except Exception as e:
                # 捕获数据库操作等异常
                messages.error(request, f'添加失败：{str(e)}')
                return render(request, self.template_name, {'form': form})

        # 表单验证失败时返回错误信息
        return render(request, self.template_name, {'form': form})

    def delete(self, request,delete_ids=[]):
        """处理DELETE请求"""
        if not delete_ids:
            material_type = request.POST.get('material_type')
            pk = request.POST.get('pk')  # 隐藏字段传递pk

            # 1. 验证模型类型
            model_class = self.MODEL_MAP.get(material_type)
            if not model_class:
                messages.error(request, "无效的物料类型")
                return redirect(self.success_url)

            # 2. 查询实例（不存在返回404）
            instance = get_object_or_404(model_class, pk=pk)

            # 3. 执行删除
            try:
                instance.raws.clear()
                instance.delete()
                messages.success(request, f"【{instance.name}】已成功删除")
            except Exception as e:
                messages.error(request, f"删除失败：{str(e)}")
        else:
            # 使用事务确保所有操作的原子性
            with transaction.atomic():
                instances=[]
                for delete_id in delete_ids:
                    pk,material_type = delete_id.split('-')
                    # 1. 验证模型类型
                    model_class = self.MODEL_MAP.get(material_type)
                    if not model_class:
                        messages.error(request, "无效的物料类型")
                        return redirect(self.success_url)
                    # 2. 查询实例（不存在返回404）
                    instance = get_object_or_404(model_class, pk=pk)

                    instances.append(instance)
                # 3. 执行删除
                for instance in instances:
                    try:
                        instance.raws.clear()
                        instance.delete()
                    except Exception as e:
                        messages.error(request, f"删除失败：{str(e)}")
                messages.success(request, f"【{len(instances)}条数据】已成功删除")
        # 4. 重定向回列表页
        return redirect(self.success_url)

class LocationsView(LoginRequiredMixin,ListView):
    """
    HomeView for website views.
    ListView 参数
    model 指定模型类（如 model = Book），自动查询该模型的所有数据
    queryset 自定义查询集（如 queryset = Book.objects.filter(price__gt=50)），优先级高于 model
    ordering 指定默认排序方式（如 ordering = ['-publish_date'] 或 ordering = '-price'）
    paginate_by 分页大小（如 paginate_by = 10 表示每页显示 10 条）
    paginate_orphans 最后一页允许的最小剩余条数（如 paginate_orphans = 3，避免最后一页只有 1-2 条数据）
    allow_empty 是否允许空列表（默认 True，空列表时正常显示；设为 False 会返回 404）
    template_name 自定义模板路径（如 template_name = 'myapp/book_list.html'）
    template_name_suffix 模板名后缀（默认 _list，结合模型名自动生成模板名，如 book_list.html）
    context_object_name 模板中使用的列表变量名（默认是模型名小写加 _list，如 book_list；可自定义为 books）
    get_context_data()	 重写该方法添加额外上下文（如传递分类、统计数据等）
    def get_context_data(self, **kwargs):
        context = super().get_context_data(** kwargs)
        context['category'] = "小说"  # 额外传递分类信息
        context['total_count'] = self.get_queryset().count()  # 总条数
        return context
    page_kwarg 分页参数名（默认 page，如 URL 中的 ?page=2；可改为 p 即 ?p=2）
    paginator_class 自定义分页器类（默认 django.core.paginator.Paginator）
    http_method_names 允许的 HTTP 方法（默认 ['get', 'head', 'options']，通常无需修改）
    extra_context 静态额外上下文（字典形式，如 extra_context = {'title': '书籍列表'}）

    """

    @classmethod
    def get_detail_data(cls, request):
        detail_data = []

        search_q = request.POST.get('q', '').strip()
        # 状态筛选（select_type）：默认 None（不过滤）
        select_type = request.POST.get('select_type')  # 启用，停用

        material_type = request.POST.get('material_type_search', 'all')
        # 物料类型与模型的映射关系（替代冗长的if-elif）
        MODEL_MAP = {
            'raw': baseModels.LocationRawModel,
            'additive': baseModels.LocationAdditiveModel,
            'packaging': baseModels.LocationPackagingModel,
            'intermediate': baseModels.LocationIntermediateModel,
            'finished': baseModels.LocationFinishedModel,

        }
        if material_type == 'all':
            raw_query = MODEL_MAP.get('raw').objects.annotate(
                wl=Value('raw', output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type).all()
            additive_query = MODEL_MAP.get('additive').objects.annotate(
                wl=Value('additive', output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type).all()
            packaging_query = MODEL_MAP.get('packaging').objects.annotate(
                wl=Value('packaging', output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type).all()
            intermediate_query = MODEL_MAP.get('intermediate').objects.annotate(
                wl=Value('intermediate', output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type).all()
            finished_query = MODEL_MAP.get('finished').objects.annotate(
                wl=Value('finished', output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type).all()
            # 3. 根据参数过滤查询集
            # 注意：union 后的查询集是 "联合查询集"，需先转为列表再过滤，或使用 Q 对象（视模型字段兼容性而定）

            # 重新合并过滤后的查询集
            queryset = raw_query.union(additive_query, all=True).union(packaging_query, all=True
                    ).union(intermediate_query, all=True).union(finished_query, all=True )
        else:
            queryset = MODEL_MAP.get(material_type).objects.annotate(
                wl=Value(material_type, output_field=CharField())
            ).filter(name__icontains=search_q, state__icontains=select_type)

        # 4. 最终排序（必须在过滤后执行，否则可能失效）
        # # 获取数据并转换为字典
        items = queryset.order_by('-create_at').all()
        for item in items:
            item_dict = model_to_dict(item)
            # 关键：添加注解的 wl 字段（item.wl 是 annotate 生成的值）
            item_dict['wl'] = item.wl  # 新增这一行
            item_dict['raws']=','.join([ raw.name for raw in  item_dict['raws'] ])
            # 处理日期时间类型（转换为字符串）
            for key, value in item_dict.items():
                if isinstance(value, timezone.datetime):
                    item_dict[key] = value.strftime('%Y-%m-%d %H:%M:%S')
                elif isinstance(value, datetime.date):
                    item_dict[key] = value.strftime('%Y-%m-%d')
            detail_data.append(item_dict)
        return detail_data

    # model = baseModels.RawModel
    # ordering = ['id']  # 必加：指定默认排序（避免 Django 警告，也能解决部分元数据初始化问题）
    context_object_name = 'wl_list'  # 可选：自定义模板变量名
    paginate_by =8  # 可选：分页
    template_name='base/locationmodel_list.html'
    def get_queryset(self):
        # 2. 获取前端提交的搜索参数（从 self.request.GET 中提取）
        # 搜索关键词（q）：默认空字符串
        search_q = self.request.GET.get('q', '').strip()
        # 状态筛选（select_type）：默认 None（不过滤）
        select_type = self.request.GET.get('select_type','1')
        material_type = self.request.GET.get('material_type_search','all')
        # 物料类型与模型的映射关系（替代冗长的if-elif）
        MODEL_MAP = {
            'raw': baseModels.LocationRawModel,
            'additive': baseModels.LocationAdditiveModel,
            'packaging': baseModels.LocationPackagingModel,
            'intermediate': baseModels.LocationIntermediateModel,
            'finished': baseModels.LocationFinishedModel,

        }

        # 1. 定义查询公共逻辑（避免重复代码）
        def get_model_query(model, material_type, search_q, select_type):
            return model.objects.annotate(
                wl=Value(material_type, output_field=CharField())  # 注解为当前物料类型
            ).filter(
                name__icontains=search_q,
                state__icontains=select_type
            ).all()

        # 3. 分情况查询并合并结果
        queryset = []
        if material_type == 'all':
            # 合并所有模型的查询结果（Python 列表合并，保留原始模型）
            for m_type, model in MODEL_MAP.items():
                queryset.extend(get_model_query(model, m_type, search_q, select_type))
        else:
            # 只查询指定模型
            model = MODEL_MAP.get(material_type)
            if model:
                queryset = get_model_query(model, material_type, search_q, select_type)

        # 后续可对 queryset 进行排序、切片等操作（注意：此时是列表，需用 Python 方法）
        try:
        # 示例：按名称排序
            queryset.sort(key=lambda x: x.create_at,reverse=True)
        except:
            queryset.order_by('-create_at')
        # 4. 最终排序（必须在过滤后执行，否则可能失效）
        return queryset

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # 额外传递分类信息
        code = 'base.LocationModel'
        context['reports'] = baseModels.ReportFileModel.objects.filter(code=code, type='1', state='1').order_by('-default').all()
        context['report_code'] = code
        context['report_type'] = '1'
        context['total_count'] = len(self.get_queryset())  # 总条数
        return context

class LocationForm(forms.Form):
    STATE_CHOICES = [
        ("1", "库区"),
        ("0", "库位"),
    ]

    # 基础字段（保持原有逻辑，优化默认值和提示）
    name = forms.CharField(
        max_length=50,
        label='货位名称',
        initial='',
        widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入品名'})
    )
    code = forms.CharField(
        max_length=50,
        label='货位编码',
        initial='',
        required=True,
        widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '可选，自动生成或手动输入'}),

    )


    type = forms.ChoiceField(
        label='货位类型',
        initial='1',
        choices=STATE_CHOICES,
        widget=forms.Select(attrs={'class': 'form-select'})
    )


    remark = forms.CharField(
        max_length=255,
        label='备注',
        initial='',
        required=False,
        widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': '可选，输入备注信息'})
    )

    # 物料类型单选框（新增禁用"包材"的逻辑）
    MATERIAL_TYPE_CHOICES = [
        ("raw", "原料"),
        ("additive", "添加剂"),
        ("packaging", "包材"),  # 需要禁用的选项
        ("intermediate", "中间品"),  # 需要禁用的选项
        ("finished", "成品"),  # 需要禁用的选项
    ]
    material_type = forms.ChoiceField(
        label="仓库类型",
        choices=MATERIAL_TYPE_CHOICES,
        widget=forms.RadioSelect(
            attrs={
                'class': 'form-check-input d-flex gap-4 align-items-center form-check-inline',  # 横向布局核心类
                # 补充：每个选项的父容器样式（通过 widget 的 template_name 或直接加类）
            }
        ),
        required=True
    )

    # 修正：Form 中用 MultipleChoiceField 替代 ManyToManyField（关联原料选择）
    # raws = forms.MultipleChoiceField(
    #     label="关联原料",
    #     required=False,  # 根据需求调整是否必填
    #     widget=forms.SelectMultiple(attrs={'class':'form-select'}),
    #     # choices=[(1,'a'),(2,'cafasfsaf')],  # 初始为空，在 __init__ 中动态加载原料数据
    #     # initial=[1,]
    # )
    # 修正：Form 中用 MultipleChoiceField 替代 ManyToManyField（关联原料选择）
    parent = forms.ChoiceField(
        label="父级",
        required=False,  # 根据需求调整是否必填
        widget=forms.Select(attrs={'class': 'form-select'}),
        # choices=[(1,'a'),(2,'cafasfsaf')],  # 初始为空，在 __init__ 中动态加载原料数据
        # initial=[1,]
    )
    def __init__(self, *args, **kwargs):
        # 接收并移除自定义参数（如果需要）
        # 比如当前用户、特定条件等
        material_type = kwargs.get('initial',{}).get('material_type','all')
        name = kwargs.get('initial', {}).get('name', '')
        super().__init__(*args, **kwargs)
        # 物料类型单选框（新增禁用"包材"的逻辑）
        self.MATERIAL_TYPE_Models = {
            "raw": baseModels.LocationRawModel,
            "additive":baseModels.LocationAdditiveModel,
            "packaging": baseModels.LocationPackagingModel,  # 需要禁用的选项
            "intermediate": baseModels.LocationIntermediateModel,  # 需要禁用的选项
            "finished": baseModels.LocationFinishedModel,  # 需要禁用的选项
            "all":baseModels.LocationRawModel,
        }
        # 动态加载原料选项
        # 从数据库查询所有原料，按名称排序
        raw_materials = self.MATERIAL_TYPE_Models.get(material_type).objects.exclude(name__in=[name,]).all().order_by('name')

        # 转换为 (value, label) 格式的元组列表
        # value 通常是模型的主键 id
        self.fields['parent'].choices =[(0,'无')]+ [
            (raw.id, raw.name) for raw in raw_materials
        ]

        # 如果需要设置动态初始值，可以在这里处理
        # # 例如：默认选中第一个原料
        # if raw_materials.exists()  :
        #     raws= self.MATERIAL_TYPE_Models.get(material_type).objects.filter(customers__name=name)
        #     self.fields['raws'].initial = [ raw.id for raw in raws.all()]

class LocationView(LoginRequiredMixin,View):
    """物料添加视图：处理原料、添加剂、包装材料的添加逻辑"""
    template_name = 'base/location.html'  # 模板路径集中管理
    success_url = 'location_list'  # 成功后重定向的URL名称（需在urls.py中定义）

    # 物料类型与模型的映射关系（替代冗长的if-elif）
    MODEL_MAP = {
        'raw': baseModels.LocationRawModel,
        'additive': baseModels.LocationAdditiveModel,
        'packaging': baseModels.LocationPackagingModel,
        'intermediate': baseModels.LocationIntermediateModel,
        'finished': baseModels.LocationFinishedModel,

    }


    def get(self, request):
        """展示空表单"""
        material_raws_map=[]
        pk=request.GET.get('pk')
        material_type=request.GET.get('material_type')  # 从URL获取模型类型（raw/additive/packaging）
        if pk and material_type:
            # 编辑场景：1. 获取对应模型类 2. 查询实例 3. 提取字段值作为initial
            model_class = self.MODEL_MAP.get(material_type)
            if not model_class:
                messages.error(request, "无效的物料类型")
                return redirect(self.success_url)
            # 获取模型实例（不存在返回404）
            instance = get_object_or_404(model_class, pk=pk)

            # 构造initial数据：表单字段名与模型字段名必须一致
            initial_data = {
                'name': instance.name,
                'code': instance.code,
                'type': instance.type,
                'state': instance.state,
                'parent':str(instance.parent.pk),
                'remark': instance.remark,
                'material_type': material_type,  # 回显物料类型单选框
                # 'raws': instance.raws,  # 多选项回显：传递ID列表
            }

            # # 传入initial数据，表单自动回显
            form = LocationForm(initial=initial_data)

        else:
            # 新增场景：空表单（若URL带model_type，可默认选中该类型）
            initial = {'material_type': material_type} if material_type else {}
            form = LocationForm(initial=initial)
            material_raws_map = {'raw':[[0,'无']]+ [[item.id,item.name] for item in baseModels.LocationRawModel.objects.filter(state='1').all()],
                                 'additive':[[0,'无']]+ [[item.id,item.name] for item in baseModels.LocationAdditiveModel.objects.filter(state='1').all()],
                                 'packaging':[[0,'无']]+  [[item.id, item.name] for item in
                                              baseModels.LocationPackagingModel.objects.filter(state='1').all()],
                                 'intermediate':[[0,'无']]+  [[item.id, item.name] for item in
                                               baseModels.LocationIntermediateModel.objects.filter(state='1').all()],
                                 'finished':[[0,'无']] + [[item.id, item.name] for item in
                                               baseModels.LocationFinishedModel.objects.filter(state='1').all()],
                                 }
        code = 'base.LocationModel'
        reports = baseModels.ReportFileModel.objects.filter(code=code, type='0', state='1').order_by(
            '-default').all()

        context={'form': form,'pk':pk,'reports':reports,'report_code':code,
                 'report_type': '0', 'material_raws_map':material_raws_map}
        return render(request, self.template_name,context)

    def post(self, request):
        """处理表单提交"""
        _method = request.POST.get('_method')
        _methods = request.POST.get('_methods')
        if _methods=='DELETE':
            delete_ids = request.POST.getlist('delete_ids')  # 隐藏字段传递pk
            return self.delete(request,delete_ids)
        elif 'DELETE' == _method:
            return self.delete(request)

        form = LocationForm(request.POST)
        if form.is_valid():
            try:
                # 提取物料类型并获取对应的模型
                material_type = form.cleaned_data.pop('material_type')
                pk = request.POST.get('pk')  # 隐藏字段传递pk
                model = self.MODEL_MAP.get(material_type)
                parent = form.cleaned_data.pop('parent')
                if not model:
                    # 处理未知物料类型的情况
                    form.add_error('material_type', '无效的物料类型')
                    return render(request, self.template_name, {'form': form})
                # 编辑场景：获取实例；新增场景：无实例
                instance = get_object_or_404(model, pk=pk) if pk else None
                if not instance:
                    if parent=='0':
                        parent=None
                    else:
                        parent=model.objects.filter(pk=parent).first()
                    # 保存数据到数据库
                    instance = model.objects.create(**form.cleaned_data,parent=parent)

                    # 添加成功提示
                    messages.success(request, '物料添加成功！')
                else:
                    # 编辑：更新实例字段
                    for key, value in  form.cleaned_data.items():
                        setattr(instance, key, value)
                    if parent == '0':
                        parent = None
                    else:
                        parent = model.objects.filter(pk=parent).first()
                    setattr(instance, 'parent', parent)
                    instance.save()
                    messages.success(request, "修改成功")
                return redirect(self.success_url)

            except Exception as e:
                # 捕获数据库操作等异常
                messages.error(request, f'添加失败：{str(e)}')
                return render(request, self.template_name, {'form': form})

        # 表单验证失败时返回错误信息
        return render(request, self.template_name, {'form': form})
    def clean_name(self,**kwargs):
        self.cleaned_data['parent']
        return self.cleaned_data['parent']
    def delete(self, request,delete_ids=[]):
        """处理DELETE请求"""
        if not delete_ids:
            material_type = request.POST.get('material_type')
            pk = request.POST.get('pk')  # 隐藏字段传递pk

            # 1. 验证模型类型
            model_class = self.MODEL_MAP.get(material_type)
            if not model_class:
                messages.error(request, "无效的物料类型")
                return redirect(self.success_url)

            # 2. 查询实例（不存在返回404）
            instance = get_object_or_404(model_class, pk=pk)

            # 3. 执行删除
            try:

                instance.delete()
                messages.success(request, f"【{instance.name}】已成功删除")
            except Exception as e:
                messages.error(request, f"删除失败：{str(e)}")
        else:
            # 使用事务确保所有操作的原子性
            with transaction.atomic():
                instances=[]
                for delete_id in delete_ids:
                    pk,material_type = delete_id.split('-')
                    # 1. 验证模型类型
                    model_class = self.MODEL_MAP.get(material_type)
                    if not model_class:
                        messages.error(request, "无效的物料类型")
                        return redirect(self.success_url)
                    # 2. 查询实例（不存在返回404）
                    instance = get_object_or_404(model_class, pk=pk)

                    instances.append(instance)
                # 3. 执行删除
                for instance in instances:
                    try:

                        instance.delete()
                    except Exception as e:
                        messages.error(request, f"删除失败：{str(e)}")
                messages.success(request, f"【{len(instances)}条数据】已成功删除")
        # 4. 重定向回列表页
        return redirect(self.success_url)